package client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.ui.*;
import com.google.gwt.user.client.Element;

/**
 * @author Ayzen
 * @version 1.0 29.04.2007 23:44:10
 */
public class Test implements EntryPoint {

  public static final String TAG =
          "<object classid=\"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000\"\n" +
          "        codebase=\"http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab\"\n" +
          "        id=\"test\" width=\"0px\" height=\"0px\">\n" +
          "  <param name=\"movie\" value=\"binary_sockets_test.swf\">\n" +
          "  <param name=\"play\" value=\"false\">\n" +
          "  <param name=\"loop\" value=\"false\">\n" +
          "  <param name=\"allowScriptAccess\" value=\"always\"/>\n" +
          "  <param name=\"FlashVars\" value=\"crossdomain={0}&id={1}\"/>\n" +
          "  <embed\n" +
          "          name=\"test\"\n" +
          "          src=\"binary_sockets_test.swf\"\n" +
          "          width=\"0px\"\n" +
          "          height=\"0px\"\n" +
          "          play=\"false\"\n" +
          "          loop=\"false\"\n" +
          "          swLiveConnect=\"true\"\n" +
          "          allowScriptAccess=\"always\"\n" +
          "          FlashVars=\"crossdomain={0}&id={1}\"\n" +
          "          pluginspage=\"http://www.macromedia.com/go/flashplayer/\">\n" +
          "  </embed>\n" +
          "</object>";

  private HTML flashTag;
  private JavaScriptObject applet;

  private String host = "127.0.0.1";
  private int port = 777;
  private boolean connected = false;

  private ScrollPanel scrollPanel;
  private VerticalPanel history = new VerticalPanel();
  private TextBox input = new TextBox();
  private Label lblHost = new Label(" Server: " + host + ':' + port);
  private Label lblAvailBytes = new Label("0");

  public void onModuleLoad() {
    initGUI();
    initApplet();
  }

  private void initApplet() {
    Panel root = RootPanel.get();

    root.remove(flashTag);

    flashTag = new HTML();
    flashTag.setSize("0", "0");

    root.add(flashTag);

    flashTag.setHTML(TAG.replaceAll("[{]0[}]", "xmlsocket://" + host + ':' + port).replaceAll("[{]1[}]", "1"));

    initFunctions(1);
  }

  private void initGUI() {
    VerticalPanel mainPanel = new VerticalPanel();
    mainPanel.setBorderWidth(1);
    scrollPanel = new ScrollPanel(history);
    scrollPanel.setSize("600", "300");
    input.setWidth("600");

    HorizontalPanel controlPanel = new HorizontalPanel();
    Button btnConnect = new Button("Connect");
    Button btnDisconnect = new Button("Disconnect");
    Button btnChangeServer = new Button("Change server");

    controlPanel.add(btnConnect);
    controlPanel.add(btnDisconnect);
    controlPanel.add(btnChangeServer);
    controlPanel.add(lblHost);
    controlPanel.add(new Label(" Available bytes: "));
    controlPanel.add(lblAvailBytes);

    VerticalPanel buttonsPanel = new VerticalPanel();
    FlowPanel firstRow = new FlowPanel();
    FlowPanel secondRow = new FlowPanel();
    FlowPanel thirdRow = new FlowPanel();
    FlowPanel fourthRow = new FlowPanel();
    Button btnReadBoolean = new Button("Read Boolean");
    Button btnReadByte = new Button("Read Byte");
    Button btnReadBytes = new Button("Read Bytes");
    Button btnReadDouble = new Button("Read Double");
    Button btnReadFloat = new Button("Read Float");
    Button btnReadInt = new Button("Read Int");
    Button btnReadShort = new Button("Read Short");
    Button btnReadUByte = new Button("Read Unsigned Byte");
    Button btnReadUInt = new Button("Read Unsigned Int");
    Button btnReadUShort = new Button("Read Unsigned Short");
    Button btnReadUTF = new Button("Read UTF");
    Button btnReadString = new Button("Read String");
    Button btnWriteBoolean = new Button("Write Boolean");
    Button btnWriteByte = new Button("Write Byte");
    Button btnWriteBytes = new Button("Write Bytes");
    Button btnWriteDouble = new Button("Write Double");
    Button btnWriteFloat = new Button("Write Float");
    Button btnWriteInt = new Button("Write Int");
    Button btnWriteShort = new Button("Write Short");
    Button btnWriteUInt = new Button("Write Unsigned Int");
    Button btnWriteUTF = new Button("Write UTF");
    Button btnWriteString = new Button("Write String");

    firstRow.add(btnReadBoolean);
    firstRow.add(btnReadByte);
    firstRow.add(btnReadBytes);
    firstRow.add(btnReadDouble);
    firstRow.add(btnReadFloat);
    firstRow.add(btnReadInt);
    secondRow.add(btnReadShort);
    secondRow.add(btnReadUByte);
    secondRow.add(btnReadUInt);
    secondRow.add(btnReadUShort);
    thirdRow.add(btnReadUTF);
    thirdRow.add(btnReadString);
    thirdRow.add(btnWriteBoolean);
    thirdRow.add(btnWriteByte);
    thirdRow.add(btnWriteBytes);
    thirdRow.add(btnWriteDouble);
    fourthRow.add(btnWriteFloat);
    fourthRow.add(btnWriteInt);
    fourthRow.add(btnWriteShort);
    fourthRow.add(btnWriteUInt);
    fourthRow.add(btnWriteUTF);
    fourthRow.add(btnWriteString);

    buttonsPanel.add(firstRow);
    buttonsPanel.add(secondRow);
    buttonsPanel.add(thirdRow);
    buttonsPanel.add(fourthRow);

    input.addKeyboardListener(new KeyboardListenerAdapter() {
      public void onKeyPress(Widget sender, char keyCode, int modifiers) {
        if (keyCode == KeyboardListener.KEY_ENTER) ;
//          sendMessage();
      }
    });

    btnConnect.addClickListener(new ClickListener() {
      public void onClick(Widget sender) {
        println(" ** Connecting to server...");
        connectImpl(host, port);
      }
    });

    btnDisconnect.addClickListener(new ClickListener() {
      public void onClick(Widget sender) {
        if (connected) {
          connected = false;
          println(" ** Disconnected from server.");
          disconnectImpl();
        }
        else
          println(" ** Not connected to server.");
      }
    });

    btnChangeServer.addClickListener(new ClickListener() {
      public void onClick(Widget sender) {
        host = inputText("Enter server ip:port", host);
        if (connected) {
          connected = false;
          println(" ** Disconnected from server.");
          disconnectImpl();
        }
        lblHost.setText(" Server: " + host + ':' + port);
        initApplet();
      }
    });

    btnReadBoolean.addClickListener(new ClickListener() {
      public void onClick(Widget sender) {
        if (connected)
          try {
            println(" << " + readBoolean());
          }
          catch (EOFException e) {
            println(" ** EOF is reached.");
          }
          finally {
            lblAvailBytes.setText(String.valueOf(available()));
          }
        else
          println(" ** " + "Not connected to server.");
      }
    });

    btnReadByte.addClickListener(new ClickListener() {
      public void onClick(Widget sender) {
        //if (connected)
          try {
            println(" << " + readByte());
          }
          catch (EOFException e) {
            println(" ** EOF is reached.");
          }
          finally {
            lblAvailBytes.setText(String.valueOf(available()));
          }
        //else
      //    println(" ** " + "Not connected to server.");
      }
    });

    btnReadBytes.addClickListener(new ClickListener() {
      public void onClick(Widget sender) {
        if (connected) {
          int length = Integer.parseInt(inputText("Enter the numbers of bytes to be read: ", String.valueOf(available())));
          byte[] bytes = new byte[length];

          try {
            readBytes(bytes);
          }
          catch (EOFException e) {
            println(" ** EOF is reached.");
          }

          String bytesStr = "";
          for (int i = 0; i < length; i++)
            bytesStr += bytes[i] + " ";
          println(" << " + bytesStr);

          lblAvailBytes.setText(String.valueOf(available()));
        }
        else
          println(" ** " + "Not connected to server.");
      }
    });

    btnReadDouble.addClickListener(new ClickListener() {
      public void onClick(Widget sender) {
        if (connected)
          try {
            println(" << " + readDouble());
          }
          catch (EOFException e) {
            println(" ** EOF is reached.");
          }
          finally {
            lblAvailBytes.setText(String.valueOf(available()));
          }
        else
          println(" ** " + "Not connected to server.");
      }
    });

    btnReadFloat.addClickListener(new ClickListener() {
      public void onClick(Widget sender) {
        if (connected)
          try {
            println(" << " + readFloat());
          }
          catch (EOFException e) {
            println(" ** EOF is reached.");
          }
          finally {
            lblAvailBytes.setText(String.valueOf(available()));
          }
        else
          println(" ** " + "Not connected to server.");
      }
    });

    btnReadInt.addClickListener(new ClickListener() {
      public void onClick(Widget sender) {
        if (connected)
          try {
            println(" << " + readInt());
          }
          catch (EOFException e) {
            println(" ** EOF is reached.");
          }
          finally {
            lblAvailBytes.setText(String.valueOf(available()));
          }
        else
          println(" ** " + "Not connected to server.");
      }
    });

    btnReadShort.addClickListener(new ClickListener() {
      public void onClick(Widget sender) {
        if (connected)
          try {
            println(" << " + readShort());
          }
          catch (EOFException e) {
            println(" ** EOF is reached.");
          }
          finally {
            lblAvailBytes.setText(String.valueOf(available()));
          }
        else
          println(" ** " + "Not connected to server.");
      }
    });

    btnReadUByte.addClickListener(new ClickListener() {
      public void onClick(Widget sender) {
        if (connected)
          try {
            println(" << " + readUnsignedByte());
          }
          catch (EOFException e) {
            println(" ** EOF is reached.");
          }
          finally {
            lblAvailBytes.setText(String.valueOf(available()));
          }
        else
          println(" ** " + "Not connected to server.");
      }
    });

    btnReadUInt.addClickListener(new ClickListener() {
      public void onClick(Widget sender) {
        if (connected)
          try {
            println(" << " + readUnsignedInt());
          }
          catch (EOFException e) {
            println(" ** EOF is reached.");
          }
          finally {
            lblAvailBytes.setText(String.valueOf(available()));
          }
        else
          println(" ** " + "Not connected to server.");
      }
    });

    btnReadUShort.addClickListener(new ClickListener() {
      public void onClick(Widget sender) {
        if (connected)
          try {
            println(" << " + readUnsignedShort());
          }
          catch (EOFException e) {
            println(" ** EOF is reached.");
          }
          finally {
            lblAvailBytes.setText(String.valueOf(available()));
          }
        else
          println(" ** " + "Not connected to server.");
      }
    });

    btnReadUTF.addClickListener(new ClickListener() {
      public void onClick(Widget sender) {
        if (connected)
          try {
            println(" << " + readUTF());
          }
          catch (EOFException e) {
            println(" ** EOF is reached.");
          }
          finally {
            lblAvailBytes.setText(String.valueOf(available()));
          }
        else
          println(" ** " + "Not connected to server.");
      }
    });

    btnReadString.addClickListener(new ClickListener() {
      public void onClick(Widget sender) {
        if (connected)
          try {
            String encoding = inputText("Enter the name of char set: ", "utf-8");
            int length = Integer.parseInt(inputText("Enter the number of bytes to be read: ", String.valueOf(available())));
            println(" << " + readString(length, encoding));
          }
          catch (EOFException e) {
            println(" ** EOF is reached.");
          }
          finally {
            lblAvailBytes.setText(String.valueOf(available()));
          }
        else
          println(" ** " + "Not connected to server.");
      }
    });

    btnWriteBoolean.addClickListener(new ClickListener() {
      public void onClick(Widget sender) {
        String msg = input.getText();
        println(" >> " + msg);
        input.setText("");

        writeBoolean(Boolean.valueOf(msg).booleanValue());
        flush();
      }
    });

    btnWriteByte.addClickListener(new ClickListener() {
      public void onClick(Widget sender) {
        String msg = input.getText();
        println(" >> " + msg);
        input.setText("");

        writeByte(Byte.parseByte(msg));
        flush();
      }
    });

    btnWriteBytes.addClickListener(new ClickListener() {
      public void onClick(Widget sender) {
        String msg = input.getText();
        println(" >> " + msg);
        String[] bytesStr = msg.split(" ");
        input.setText("");

        byte[] bytes = new byte[bytesStr.length];
        for (int i = 0; i < bytesStr.length; i++)
          bytes[i] = Byte.parseByte(bytesStr[i]);

        writeBytes(bytes);
        flush();
      }
    });

    btnWriteDouble.addClickListener(new ClickListener() {
      public void onClick(Widget sender) {
        String msg = input.getText();
        println(" >> " + msg);
        input.setText("");

        writeDouble(Double.parseDouble(msg));
        flush();
      }
    });

    btnWriteFloat.addClickListener(new ClickListener() {
      public void onClick(Widget sender) {
        String msg = input.getText();
        println(" >> " + msg);
        input.setText("");

        writeFloat(Float.parseFloat(msg));
        flush();
      }
    });

    btnWriteInt.addClickListener(new ClickListener() {
      public void onClick(Widget sender) {
        String msg = input.getText();
        println(" >> " + msg);
        input.setText("");

        writeInt(Integer.parseInt(msg));
        flush();
      }
    });

    btnWriteShort.addClickListener(new ClickListener() {
      public void onClick(Widget sender) {
        String msg = input.getText();
        println(" >> " + msg);
        input.setText("");

        writeShort(Short.parseShort(msg));
        flush();
      }
    });

    btnWriteUInt.addClickListener(new ClickListener() {
      public void onClick(Widget sender) {
        String msg = input.getText();
        println(" >> " + msg);
        input.setText("");

        writeUnsignedInt(Long.parseLong(msg));
        flush();
      }
    });

    btnWriteUTF.addClickListener(new ClickListener() {
      public void onClick(Widget sender) {
        String msg = input.getText();
        println(" >> " + msg);
        input.setText("");

        writeUTF(msg);
        flush();
      }
    });

    btnWriteString.addClickListener(new ClickListener() {
      public void onClick(Widget sender) {
        String msg = input.getText();
        String charSet = inputText("Enter the name of char set: ", "utf-8");

        println(" >> " + msg);
        input.setText("");

        writeString(msg, charSet);
        flush();
      }
    });

    mainPanel.add(scrollPanel);
    mainPanel.add(input);
    mainPanel.add(controlPanel);
    mainPanel.add(buttonsPanel);

    RootPanel.get().add(mainPanel);

    println("Welcome to Sockets test application :)");
  }

  // Util functions

  private void println(String message) {
    history.add(new Label(message));
    scrollToBottom();
  }

  public void scrollToBottom() {
    scrollToBottomImpl(scrollPanel.getElement());
  }

  private native void scrollToBottomImpl(Element element) /*-{
    element.scrollTop = element.scrollHeight;
  }-*/;

  private native String inputText(String message, String defText)/*-{
    var text = prompt(message, defText);
    if (text != null && text != "")
      return text;
    else
      return defText;
  }-*/;

  // Event dispatchers

  private void onConnect() {
    connected = true;
    writeByte(1);
    flush();
    println(" ** Connected to server.");
  }

  private void onClose() {
    println(" ** Server closed connection.");
    connected = false;
  }

  private void onIOError(String message) {
    println(" ** Input/output error: " + message + ".");
  }

  private void onSecurityError(String message) {
    println(" ** Security error: " + message + ".");
  }

  private void onData(int loadedBytes) {
    if (connected) {
      int availBytes = available();
      lblAvailBytes.setText(String.valueOf(availBytes));
      println(" << Received " + loadedBytes + " byte(s). Total bytes: " + availBytes);
    }
  }

  // Functions for Flash support

  private native void initFunctions(int id)/*-{
    var instance = this;

    $wnd["flashbin_" + id + "_onAppletLoad"] = function() {
      instance.@client.Test::applet = $doc["test"];
    }
    $wnd["flashbin_" + id + "_onConnect"] = function(success) {
      instance.@client.Test::onConnect()();
    }
    $wnd["flashbin_" + id + "_onClose"] = function() {
      instance.@client.Test::onClose()();
    }
    $wnd["flashbin_" + id + "_onData"] = function(loadedBytes) {
      instance.@client.Test::onData(I)(loadedBytes);
    }
    $wnd["flashbin_" + id + "_onSecurityError"] = function(msg) {
      instance.@client.Test::onSecurityError(Ljava/lang/String;)(msg);
    }
    $wnd["flashbin_" + id + "_onIOError"] = function(msg) {
      instance.@client.Test::onIOError(Ljava/lang/String;)(msg);
    }
  }-*/;

  private native void connectImpl(String ip, int port)/*-{
    this.@client.Test::applet.connect(ip, port);
  }-*/;

  private native void disconnectImpl()/*-{
    this.@client.Test::applet.close();
  }-*/;

  private native int available()/*-{
    return this.@client.Test::applet.bytesAvailable();
  }-*/;

  private native void flush()/*-{
    return this.@client.Test::applet.flush();
  }-*/;

  private native boolean connected()/*-{
    return this.@client.Test::applet.connected();
  }-*/;

  private int skip(int bytes) throws EOFException {
    if (bytes <= 0)
      return 0;

    byte[] buffer = new byte[bytes];
    return readBytes(buffer, 0, bytes);
  }

  private boolean readBoolean() throws EOFException {
    if (available() < 1)
      throw new EOFException();
    return readBooleanImpl();
  }

  private native boolean readBooleanImpl()/*-{
    return this.@client.Test::applet.readBoolean();
  }-*/;

  private byte readByte() throws EOFException {
//    if (available() < 1)
//      throw new EOFException();
    return readByteImpl();
  }

  private native byte readByteImpl()/*-{
    return this.@client.Test::applet.readByte();
  }-*/;

  private int readBytes(byte[] bytes) throws EOFException {
    return readBytes(bytes, 0, bytes.length);
  }

  private int readBytes(byte[] bytes, int offset, int length) throws EOFException {
    if (bytes == null)
      throw new NullPointerException();

    if (offset < 0 || length < 0 || offset + length > bytes.length)
      throw new IndexOutOfBoundsException();

    if (length == 0)
      return 0;

    int availBytes = available();
    if (availBytes == 0)
      return -1;

    // If available bytes < than a length, it will read all available bytes
    if (availBytes < length)
      length = availBytes;

    byte[] readedBytes = new byte[length];

    if (GWT.isScript())
      readBytesImpl(readedBytes, length);
    else {
      String[] bytesStr = readBytesHostedImpl(length).split(" ");
      for (int i = 0; i < bytesStr.length; i++)
        readedBytes[i] = Byte.parseByte(bytesStr[i]);
    }

    for (int i = 0; i < length; i++)
      bytes[offset + i] = readedBytes[i];

    return length;
  }

  private native void readBytesImpl(byte[] bytes, int length)/*-{
    var jsArray = this.@client.Test::applet.readBytes(length);
    alert(jsArray);
    for (i = 0; i < length; i++)
      bytes[i] = jsArray[i];
  }-*/;

  /**
   * Workaround for bug with passing arrays via JSNI in Hosted Browser
   */
  private native String readBytesHostedImpl(int length)/*-{
    var result = "";
    var jsArray = this.@client.Test::applet.readBytes(length);

    for (i = 0; i < length; i++)
      result += jsArray[i] + " ";

    return result;
  }-*/;

  private double readDouble() throws EOFException {
    if (available() < 8)
      skip(8); // Will skip bytes and throw EOFException
    return readDoubleImpl();
  }

  private native double readDoubleImpl()/*-{
    return this.@client.Test::applet.readDouble();
  }-*/;

  private float readFloat() throws EOFException {
    if (available() < 4)
      skip(4); // Will skip bytes and throw EOFException
    return readFloatImpl();
  }

  private native float readFloatImpl()/*-{
    return this.@client.Test::applet.readFloat();
  }-*/;

  private int readInt() throws EOFException {
    if (available() < 4)
      skip(4); // Will skip bytes and throw EOFException
    return readIntImpl();
  }

  private native int readIntImpl()/*-{
    return this.@client.Test::applet.readInt();
  }-*/;

  private short readShort() throws EOFException {
    if (available() < 2)
      skip(2); // Will skip bytes and throw EOFException
    return readShortImpl();
  }

  private native short readShortImpl()/*-{
    return this.@client.Test::applet.readShort();
  }-*/;

  private int readUnsignedByte() throws EOFException {
    if (available() < 1)
      throw new EOFException();
    return readUnsignedByteImpl();
  }

  private native int readUnsignedByteImpl()/*-{
    return this.@client.Test::applet.readUnsignedByte();
  }-*/;

  private long readUnsignedInt() throws EOFException {
    if (available() < 4)
      skip(4); // Will skip bytes and throw EOFException
    return readUnsignedIntImpl();
  }

  private native long readUnsignedIntImpl()/*-{
    return this.@client.Test::applet.readUnsignedInt();
  }-*/;

  private int readUnsignedShort() throws EOFException {
    if (available() < 2)
      skip(2); // Will skip bytes and throw EOFException
    return readUnsignedShortImpl();
  }

  private native int readUnsignedShortImpl()/*-{
    return this.@client.Test::applet.readUnsignedInt();
  }-*/;

  private String readUTF() throws EOFException {
    String utfStr = readUTFImpl();
    if (utfStr == null)
      throw new EOFException();
    return utfStr;
  }

  private native String readUTFImpl()/*-{
    return this.@client.Test::applet.readUTF();
  }-*/;

  private String readString(int length, String charSet) throws EOFException {
    if (available() < length) {
      skip(length); // Will skip bytes and throw EOFException
    }
    return readStringImpl(length, charSet);
  }

  private native String readStringImpl(int length, String charSet)/*-{
    return this.@client.Test::applet.readMultiByte(length, charSet);
  }-*/;

  private native void writeBoolean(boolean value)/*-{
    this.@client.Test::applet.writeBoolean(value);
  }-*/;

  private native void writeByte(int value)/*-{
    this.@client.Test::applet.writeByte(value);
  }-*/;

  private void writeBytes(byte[] bytes) {
    writeBytes(bytes, 0, bytes.length);
  }

  private void writeBytes(byte[] bytes, int offset, int length) {
    if (bytes == null)
      throw new NullPointerException();

    if (offset < 0 || length < 0 || offset + length > bytes.length)
      throw new IndexOutOfBoundsException();

    if (length == 0)
      return;

    String bytesStr = String.valueOf(bytes[offset]);
    for (int i = 1; i < length; i++)
      bytesStr += " " + bytes[offset + i];

    writeBytesImpl(bytesStr);
  }

  private native void writeBytesImpl(String bytesStr)/*-{
    this.@client.Test::applet.writeBytes(bytesStr);
  }-*/;

  private native void writeDouble(double value)/*-{
    this.@client.Test::applet.writeDouble(value);
  }-*/;

  private native void writeFloat(float value)/*-{
    this.@client.Test::applet.writeFloat(value);
  }-*/;

  private native void writeInt(int value)/*-{
    this.@client.Test::applet.writeInt(value);
  }-*/;

  private native void writeShort(short value)/*-{
    this.@client.Test::applet.writeShort(value);
  }-*/;

  private native void writeUnsignedInt(long value)/*-{
    this.@client.Test::applet.writeUnsignedInt(value);
  }-*/;

  private native void writeUTF(String value)/*-{
    this.@client.Test::applet.writeUTF(value);
  }-*/;

  private native void writeString(String value, String charSet)/*-{
    this.@client.Test::applet.writeMultiByte(value, charSet);
  }-*/;

  private native boolean checkIfLoaded()/*-{
    var app = $doc["test"];
    if (typeof(app) != "undefined") {
      return app.PercentLoaded() == 100;
    } else {
      return false;
    }
  }-*/;

}
